# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# (C) 2012 Remek Zajac
#!/usr/bin/python

import math

#local modules
import pyGeometry

class Path(object):
    def __init__(self, parent):
        self.clear()
        self.parent = parent

    def clear(self):
        self.m_milestones = []
        self.m_gpxRegion = pyGeometry.GeoRegion()
        self.m_maxSpeed = 0
        self.m_minSpeed = 0
        self.m_avgSpeed = 0
        self.m_distanceSoFar = 0
        self.m_tripTimeSoFar = 0
        self.m_lastMilestone = None

    def append(self, milestone):
        if self.m_lastMilestone:
            milestone.calculateStats(self.m_lastMilestone)
        else:
            self.m_lastMilestone = milestone.copy()
        self.m_milestones.append(milestone)
        self.m_gpxRegion.mergeInto(milestone.m_latlon)
        if milestone.m_reliable:
            self.m_lastMilestone = milestone.copy()
            if milestone.m_distanceFromPrevious > 0 or milestone.m_speedFromPrevious > 0:        
                if self.m_maxSpeed < self.m_lastMilestone.m_speedFromPrevious:
                    self.m_maxSpeed = self.m_lastMilestone.m_speedFromPrevious
                if self.m_minSpeed == 0 or self.m_minSpeed > self.m_lastMilestone.m_speedFromPrevious:
                    self.m_minSpeed = self.m_lastMilestone.m_speedFromPrevious
            
                self.m_avgSpeed =   ((self.m_avgSpeed*self.m_distanceSoFar)+\
                                     (self.m_lastMilestone.m_speedFromPrevious*self.m_lastMilestone.m_distanceFromPrevious))/\
                                    (self.m_distanceSoFar+self.m_lastMilestone.m_distanceFromPrevious)
                self.m_distanceSoFar += self.m_lastMilestone.m_distanceFromPrevious
                self.m_tripTimeSoFar += self.m_lastMilestone.m_timestamp 

    def region(self):
        return self.m_gpxRegion

    def speedStats(self):
        return (self.m_maxSpeed, self.m_avgSpeed, self.m_minSpeed)

    def draw(self, cr):
        pathTool = self.parent.drawingToolkit().pathTool("tripWithSpeed")
        pathTool.init(cr)
        maxSpeed, avgSpeed, minSpeed = self.speedStats()
        speedModifierPercent = 50
        _milestone = None
        for milestone in self.m_milestones:          
            pix = self.parent.latlon2pix(milestone.m_latlon)
            if maxSpeed > 0 and milestone.m_speedFromPrevious == 0 and milestone.m_reliable:
                pathTool.markStationary(pix)
                continue
            if _milestone:               
                _milestone.add(milestone) 
            else:
                _milestone = milestone.copy()
            if maxSpeed > 0:
                speedModifierPercent = (100*(_milestone.m_speedFromPrevious)/(maxSpeed))
            if pathTool.draw(pix, speedModifierPercent):
                _milestone = None

    def invalidate(self, calledBy):
        pass

    @staticmethod
    def latlondelta2km(latlonstart, latlonend):
        R = 6371.0 #km
        dLat = math.radians(latlonend[0]-latlonstart[0])
        dLon = math.radians(latlonend[1]-latlonstart[1])
        latstart = math.radians(latlonstart[0])
        latend = math.radians(latlonend[0])
        a = math.sin(dLat/2) * math.sin(dLat/2) + math.sin(dLon/2) * math.sin(dLon/2) * math.cos(latstart) * math.cos(latend); 
        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)); 
        return R * c    

    class Milestone:   
        def __init__(self, latlon, timestamp):
            self.m_latlon = latlon
            self.m_timestamp = timestamp
            self.m_distanceFromPrevious = None
            self.m_timeFromPrevious = None
            self.m_speedFromPrevious = 0
            self.m_accelerationFromPrevious = None
            self.m_reliable = False

        def calculateStats(self, referenceMilestone):
            assert(referenceMilestone)
            assert(self.m_timestamp>=referenceMilestone.m_timestamp) #must be given in sequence
            if not self.m_distanceFromPrevious:
                self.m_distanceFromPrevious = Path.latlondelta2km(referenceMilestone.m_latlon, self.m_latlon)
                if self.m_timestamp:
                    self.m_timeFromPrevious = self.m_timestamp - referenceMilestone.m_timestamp                
                    if self.m_timeFromPrevious > 0:
                        self.m_speedFromPrevious = 1.0*self.m_distanceFromPrevious/(self.m_timeFromPrevious/3600)
                        #speed, as calculated above is, by definition, an average speed maintained through the relevant segment.
                        #Assuming then that the subject travels enters the current segment with that average speed (calculated for the
                        #previous segment, as m_lastSpeed) the subject, in order to reach the average speed in the current segment
                        #would need reach that average speed in half the time (hence dTime/2).
                        #Note that this doesn't take the direction of movement into account.
                        self_m_accelerationFromPrevious = (self.m_speedFromPrevious-referenceMilestone.m_speedFromPrevious)/(self.m_timeFromPrevious/2)
                    else:
                        self.m_speedFromPrevious = 0.0
                    self.m_reliable = True

        def add(self, milestone):
            self.m_latlon = milestone.m_latlon
            self.m_timestamp = milestone.m_timestamp
            #now, self.m_speedFromPrevious and self.m_distanceFromPrevious may be None, if self is the first milestone only
            #speedFromPrevious may be None for followup milestones if time is not registered (say for routing rather than recording).
            #by defintion, the next milestone can't be first.
            assert(milestone.m_distanceFromPrevious and (milestone.m_speedFromPrevious != None or milestone.m_timestamp == None))
            if milestone.m_distanceFromPrevious:
                assert(milestone.m_speedFromPrevious != None)
                #weigh the average
                self.m_speedFromPrevious = ((self.m_speedFromPrevious*self.m_distanceFromPrevious)+\
                                        (milestone.m_speedFromPrevious*milestone.m_distanceFromPrevious))/\
                                        (milestone.m_distanceFromPrevious+self.m_distanceFromPrevious)
                self.m_distanceFromPrevious += milestone.m_distanceFromPrevious                
            else:
                self.m_speedFromPrevious = milestone.m_speedFromPrevious
                self.m_distanceFromPrevious = milestone.m_distanceFromPrevious
        
        def speed(self):
            if self.m_speedReliable:
                return self.m_reliable
            else:
                return None

        def copy(self):
            milestone = Path.Milestone(self.m_latlon, self.m_timestamp)
            milestone.m_distanceFromPrevious = self.m_distanceFromPrevious
            milestone.m_timeFromPrevious = self.m_timeFromPrevious
            milestone.m_speedFromPrevious = self.m_speedFromPrevious
            milestone.m_accelerationFromPrevious = self.m_accelerationFromPrevious
            milestone.m_reliable = self.m_reliable
            return milestone

         
        

